package com.nervenets.general.service.impl;

import com.nervenets.general.Global;
import com.nervenets.general.entity.KeyValue;
import com.nervenets.general.hibernate.DomainObject;
import com.nervenets.general.service.BaseService;
import com.nervenets.general.service.CacheableService;
import com.nervenets.general.web.params.FormParams;
import com.nervenets.general.web.params.Params;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;

import static com.nervenets.general.utils.StringUtils.formatKey;

@Slf4j
@Transactional(rollbackFor = Exception.class)
public abstract class CacheableServiceImpl<T extends DomainObject, Dao extends JpaRepository<T, Long> & JpaSpecificationExecutor<T>> extends BaseServiceImpl<T, Dao> implements CacheableService<T> {
    @Autowired
    public CacheManager cacheManager;

    @Override
    @Transactional
    @CacheEvict(value = Global.Constants.CACHE_KEY, key = "@us.formatKey(#root.targetClass.simpleName, #params.formId)")
    public <S extends BaseService<T>> T formSave(FormParams<T, S> params, HttpServletRequest request, HttpServletResponse response) {
        return super.formSave(params, request, response);
    }

    @Override
    @Transactional
    @CacheEvict(value = Global.Constants.CACHE_KEY, key = "@us.formatKey(#root.targetClass.simpleName, #var1.id)")
    public T save(T var1, Params... params) {
        return super.save(var1, params);
    }

    @Override
    @Transactional
    @CacheEvict(value = Global.Constants.CACHE_KEY, key = "@us.formatKey(#root.targetClass.simpleName, #id)")
    public int updateById(long id, String field, Object value) {
        return super.updateById(id, field, value);
    }

    @Override
    @Transactional
    @CacheEvict(value = Global.Constants.CACHE_KEY, key = "@us.formatKey(#root.targetClass.simpleName, #id)")
    public int updateById(long id, KeyValue... keyValues) {
        return super.updateById(id, keyValues);
    }

    @Override
    @Transactional
    @CacheEvict(value = Global.Constants.CACHE_KEY, key = "@us.formatKey(#root.targetClass.simpleName, #var1)")
    public void deleteById(long var1) {
        super.deleteById(var1);
    }

    @Override
    @CacheEvict(value = Global.Constants.CACHE_KEY, key = "@us.formatKey(#root.targetClass.simpleName, #var1.id)")
    public void delete(T var1) {
        super.delete(var1);
    }

    @Override
    @Transactional
    public List<T> saveAll(List<T> vars, Params... params) {
        Cache cache = cacheManager.getCache(Global.Constants.CACHE_KEY);

        List<T> results = new ArrayList<>();
        vars.forEach(t -> {
            results.add(save(t, params));
            if (null != cache) cache.evict(formatKey(getClass().getSimpleName(), t.getId()));
        });
        return results;
    }

    @Override
    @Transactional
    public void deleteAll(Iterable<? extends T> entities) {
        Cache cache = cacheManager.getCache(Global.Constants.CACHE_KEY);
        entities.forEach(t -> {
            this.delete(t);
            if (null != cache) cache.evict(formatKey(getClass().getSimpleName(), t.getId()));
        });
    }

    @Override
    @Cacheable(value = Global.Constants.CACHE_KEY, key = "@us.formatKey(#root.targetClass.simpleName, #var1)", sync = true)
    public T findByCache(long var1) {
        return findById(var1).orElse(null);
    }

    protected void cacheUpdate(long id) {
        Cache cache = cacheManager.getCache(Global.Constants.CACHE_KEY);
        if (null != cache) {
            cache.put(formatKey(getClass().getSimpleName(), id), findById(id).orElse(null));
        }
    }
}
